﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

using OCXS;
using Conf = OCXS.OCXSServiceLibrary.Configuration;
using OCXS.OCXSServiceLibrary.OPCXMLDA10;

namespace OCXS {

	namespace OCXSServiceLibrary {
        
		public partial class OCXSService {

			/// <summary>
            /// <para>
			/// Implements OPC XML DA 1.0 Write service. 
            /// </para>
            /// <para>
            /// See OPC XML DA 1.0 specification for more information about Write service.
            /// </para>
			/// </summary>
            /// <remarks>
            /// <para>
            ///     <b>Following there's a list of implemented (or not) standard features.</b>
            /// </para>
            /// <para>
            ///     The following <c>WriteRequest</c> standard features are (or not) implemented.
            /// 
            ///     <ul>
            ///         <li>
            ///             <b>ItemList: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ItemPath: </b> ignored. Specific per-Item path and name are considered.</li>
            ///                 <li>
            ///                     <b>Items: </b> the following features are (or not) supported:
            ///                     <ul>
            ///                         <li><b>ClientItemHandle: </b> supported.</li>
            ///                         <li><b>DiagnosticInfo: </b> supported.</li>
            ///                         <li><b>ItemName: </b> supported.</li>
            ///                         <li><b>ItemPath: </b> supported.</li>
            ///                         <li><b>Quality: </b> ignored.</li>
            ///                         <li><b>ResultID: </b> ignored.</li>
            ///                         <li><b>Timestamp: </b> ignored.</li>
            ///                         <li><b>TimestampSpecified: </b> ignored</li>
            ///                         <li><b>Value: </b> supported.</li>
            ///                         <li><b>ValueTypeQualifier: </b> <c>xs:float</c> expected.</li>
            ///                     </ul>
            ///                 </li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>Options: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>LocaleID: </b> supported.</li>
            ///                 <li><b>RequestDeadline: </b> not supported.</li>
            ///                 <li><b>RequestDeadlineSpecified: </b> ignored.</li>
            ///                 <li><b>ReturnDiagnosticInfo: </b> supported.</li>
            ///                 <li><b>ReturnErrorText: </b> supported.</li>
            ///                 <li><b>ReturnItemName: </b> supported.</li>
            ///                 <li><b>ReturnItemPath: </b> supported.</li>
            ///                 <li><b>ReturnItemTime: </b> supported.</li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li><b>ReturnValuesOnReply: </b> supported.</li>
            ///     </ul>
            /// 
            /// </para>
            /// 
            /// <para>
            ///     The following <c>WriteResponse</c> standard features are (or not) implemented.
            ///     <ul>
            ///         
            ///         <li>
            ///             <b>Errors: </b> One error per time is reported. The following errors are supported:
            ///             <ul>
            ///                 <li><c>E_OUTOFMEMORY</c></li>
            ///                 <li><c>E_FAIL</c></li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>RItemList: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li>
            ///                     <b>ItemList: </b> the following features are (or not) supported:
            ///                     <ul>
            ///                         <li>
            ///                             <b>Items: </b> the following features are (or not) supported:
            ///                             <ul>
            ///                                 <li><b>ClientItemHandle: </b> supported.</li>
            ///                                 <li><b>DiagnosticInfo: </b> supported.</li>
            ///                                 <li><b>ItemName: </b> supported.</li>
            ///                                 <li><b>ItemPath: </b> supported.</li>
            ///                                 <li><b>Quality: </b> supported (static values: <c>good</c>).</li>
            ///                                 <li><b>ResultID: </b> supported. Only <c>E_UNKNOWNITEMNAME</c> error is supported.</li>
            ///                                 <li><b>Timestamp: </b> supported.</li>
            ///                                 <li><b>TimestampSpecified: </b> supported</li>
            ///                                 <li><b>Value: </b> supported.</li>
            ///                                 <li><b>ValueTypeQualifier: </b> supported (<c>xs:float</c>).</li>
            ///                             </ul>
            ///                         </li>
            ///                         <li><b>ItemPath: </b> ignored. Specific per-Item path and name are considered.</li>
            ///                     </ul>
            ///                 </li>
            ///             </ul>
            ///         </li>
            ///         
            ///         <li>
            ///             <b>WriteResult: </b> the following features are (or not) supported:
            ///             <ul>
            ///                 <li><b>ClientRequestHandle: </b> supported.</li>
            ///                 <li><b>RcvTime: </b> supported (evaluated as first instruction).</li>
            ///                 <li><b>ReplyTime: </b> supported (evaluated as last instruction).</li>
            ///                 <li><b>RevisedLocaleID: </b> supported (<c>en-us</c>).</li>
            ///                 <li><b>ServerState: </b> supported (<c>running</c>).</li>
            ///             </ul>
            ///         </li>
            ///         
            ///     </ul>
            /// </para>
            /// 
            /// <code>
            /// // The following code can be found in OCXSClient and 
            /// // shows how to call Write service.
            /// class Program {
            ///
            ///     private const string EndpointAddress = "http://localhost/OCXS/OCXS.svc";
            ///     
            ///     static void Main(string[] args) {
            ///     
            ///         // Writes default items specified in Write()
            ///         // without returning values just written in
            ///         // response.
            ///         Write("en-us", "0x00FF", false);
            ///     }
            ///     
            /// public static WriteResponse Write(
            /// 	string localeID,
            /// 	string clientRequestHandle,
            /// 	bool returnValues,
            /// 	bool returnItemName = true,
            /// 	bool returnItemPath = true,
            /// 	bool returnItemTime = true,
            /// 	bool returnDiagnosticInfo = false,
            /// 	bool returnErrorText = false,
            /// 	WriteRequestItemList requestItemList = null,
            /// 	// Not supported features down here
            /// 	bool requestDeadlineSpecified = false) 
            /// {
            /// 	// Get the channel.
            /// 	// GetOCXSChannel can be found in OCXSClient.
            /// 	IOCXSService svc = GetOCXSChannel(EndpointAddress);
            /// 
            /// 	// Create request options, for ReadRequest.
            /// 	RequestOptions ro = new RequestOptions();
            /// 	ro.ClientRequestHandle = clientRequestHandle;
            /// 	ro.LocaleID = localeID;
            /// 	ro.RequestDeadline = DateTime.Parse("2001-09-11");
            /// 	ro.RequestDeadlineSpecified = requestDeadlineSpecified;
            /// 	ro.ReturnDiagnosticInfo = returnDiagnosticInfo;
            /// 	ro.ReturnErrorText = returnErrorText;
            /// 	ro.ReturnItemName = returnItemName;
            /// 	ro.ReturnItemPath = returnItemPath;
            /// 	ro.ReturnItemTime = returnItemTime;
            /// 
            /// 	// Let's create a valid WriteRequestItemList if not provided.
            /// 	// This way is shown how to create a valid ReadRequestItemList.
            /// 	WriteRequestItemList requestItems = requestItemList;
            /// 	if (requestItems == null) {
            /// 		requestItems = new WriteRequestItemList();
            /// 
            /// 		// requestItems.Items is an ItemValue[].
            /// 		// There are two 'main ways' to obtain an ItemValue[]:
            /// 		//   1. Creating a static fixed size array.
            /// 		//
            /// 		//   2. Create a List<ItemValue> and call ToArray() method
            /// 		//      when finished adding ItemValues.
            /// 		//
            /// 		// The solution (1) follows.
            /// 		requestItems.Items = new ItemValue[2];
            /// 
            /// 		requestItems.Items[0] = new ItemValue();
            /// 		requestItems.Items[0].DiagnosticInfo = null;
            /// 		requestItems.Items[0].ItemPath = "Root/Fold1/";
            /// 		requestItems.Items[0].ItemName = "MyVar1";
            /// 		requestItems.Items[0].ClientItemHandle = clientRequestHandle;
            /// 		requestItems.Items[0].ResultID = null;
            /// 		requestItems.Items[0].ValueTypeQualifier = new System.Xml.XmlQualifiedName("float", "xs");
            /// 		requestItems.Items[0].Value = "28";
            /// 		requestItems.Items[0].Timestamp = DateTime.Now;
            /// 		requestItems.Items[0].TimestampSpecified = true;
            /// 		requestItems.Items[0].Quality = null;
            /// 
            /// 		requestItems.Items[1] = new ItemValue();
            /// 		requestItems.Items[1].DiagnosticInfo = null;
            /// 		requestItems.Items[1].ItemPath = "Root/Fold1/";
            /// 		requestItems.Items[1].ItemName = "MyVar666";
            /// 		requestItems.Items[1].ClientItemHandle = clientRequestHandle;
            /// 		requestItems.Items[1].ResultID = null;
            /// 		requestItems.Items[1].ValueTypeQualifier = new System.Xml.XmlQualifiedName("float", "xs");
            /// 		requestItems.Items[1].Value = "42";
            /// 		requestItems.Items[1].Timestamp = DateTime.Now;
            /// 		requestItems.Items[1].TimestampSpecified = true;
            /// 		requestItems.Items[1].Quality = null;
            /// 
            /// 
            /// 		requestItems.ItemPath = null;
            /// 	}
            /// 
            /// 	// Create the request. Mind that formal parameters with default 
            /// 	// values usually correspond to not supported features.
            /// 	WriteRequest WR = new WriteRequest(ro, requestItems, returnValues);
            /// 
            ///     // Perform the call to service and get the response.
            /// 	WriteResponse res = svc.Write(WR);
            /// 
            ///     // Print the response.
            ///     // PrintResponse can be found in OCXSClient.
            /// 	PrintResponse(res);
            /// 
            /// 	return res;
            /// }
            /// </code>
            /// 
            /// </remarks>
            /// <param name="request">The <c>WriteRequest</c> issued by client according to OCP XML DA 1.0 specification.</param>
            /// <returns>A <c>WriteResponse</c> according to OPC XML DA 1.0 specification.</returns>
			public WriteResponse Write(WriteRequest request) {

                WriteResponse WR = null;
                // Get the reply base
                ReplyBase RB = this.GetReplyBase(request.Options.ClientRequestHandle, request.Options.LocaleID);

                try {
                    // Foreach element requested need to get the corresponding ItemValue
                    ItemValue[] items = new ItemValue[request.ItemList.Items.Length];

                    for (int i = 0; i < request.ItemList.Items.Length; i++)
                        // If null is returned => variable invalid
                        items[i] = this.WriteItem(request.ItemList.Items[i], 
                                                  request.ReturnValuesOnReply,
                                                  request.Options.ReturnItemName,
                                                  request.Options.ReturnItemPath,
                                                  request.Options.ReturnItemTime,
                                                  request.Options.ReturnDiagnosticInfo);

                    ReplyItemList RIL = new ReplyItemList();
                    RIL.Items = items;
                    RIL.Reserved = null;

                    WR = new WriteResponse(RB, RIL, null);

                    RB.ReplyTime = DateTime.Now; /* Before returning */

                    return WR;

                } catch (OutOfMemoryException e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new WriteResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_OUTOFMEMORY", e.Message) });

                } catch (Exception e) {

                    RB.ReplyTime = DateTime.Now;

                    // Return proper out of memory error message.
                    return new WriteResponse(
                            RB,
                            null,
                            new OPCError[] { this.BuildOPCError("E_FAIL", e.Message) });
                }
			} /* Write */

            /// <summary>
            /// Performs write operation in the variable pool according to provided parameters specified
            /// in the request.
            /// </summary>
            /// <param name="iv">The value to write, also containing variable identifier.</param>
            /// <param name="returnValue">If <c>true</c> the new value will be returned in the write response.</param>
            /// <param name="retName">If <c>true</c> the ItemName will be returned in the write response.</param>
            /// <param name="retPath">If <c>true</c> the ItemPath will be returned in the write response.</param>
            /// <param name="returnTime">If <c>true</c> the Timestamp will be returned in the write response.</param>
            /// <param name="returnDiag">If <c>true</c> the DiagnosticInfo will be returned in the write response.</param>
            /// <returns>The written <c>ItemValue</c>.</returns>
            private ItemValue WriteItem(
                                ItemValue iv,
                                bool returnValue = false,
                                bool retName = false,                
                                bool retPath = false, 
                                bool returnTime = false,
                                bool returnDiag = false) {

                KeyValuePair<Conf.VariableType, Object> v;

                bool gotV = this.dataStore.TryGetValue((iv.ItemPath + iv.ItemName), out v);

                Conf.VariableType retVar = null;

                if (gotV) {
                    /* Critical Section */
                    lock (v.Value) {

                        // Write new value, In any cases the data is read
                        v.Key.VariableValue = iv.Value;
                        retVar = (Conf.VariableType)v.Key.Clone();

                    }
                }
                    
                ItemValue newItemValue = new ItemValue();
                newItemValue.DiagnosticInfo = (returnDiag) ? "Diagnostics ok! Hurray!" : null;
                newItemValue.ItemPath = (retPath) ? iv.ItemPath : null;
                newItemValue.ItemName = (retName) ? iv.ItemName : null;
                newItemValue.ClientItemHandle = iv.ClientItemHandle;
                
                newItemValue.ResultID = (gotV) ? 
                                         null : 
                                         new System.Xml.XmlQualifiedName("E_UNKNOWNITEMNAME", "http://opcfoundation.org/webservices/XMLDA/1.0/");
                
                newItemValue.ValueTypeQualifier = new System.Xml.XmlQualifiedName("float", "xs");
                newItemValue.Value = (returnValue && gotV) ? retVar.VariableValue : null;
                newItemValue.Timestamp = (returnTime) ? DateTime.Now : DateTime.MinValue;
                newItemValue.TimestampSpecified = returnTime;
                newItemValue.Quality = new OPCQuality();
                newItemValue.Quality.QualityField = qualityBits.good;
                newItemValue.Quality.LimitField = limitBits.none;
                newItemValue.Quality.VendorField = 0;

                return newItemValue;
		    }

        }
	}

}
